﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;

using Survey.Common;

using ProjectBase.Core;
using ProjectBase.Mvc.Controllers;

namespace Survey.Business
{
    /// <summary>
    ///     IObjectBinder provides any generic T ("Entity") binding of data passed as an IValueProvider.
    ///     This IObjectBinder is available not only for    UI,
    ///     and therefor it could be reused for other data transfers
    ///     (e.g. import from MS Excel)
    /// </summary>
    public class ObjectBinder : IObjectBinder
    {
        #region members
        IModelBinder _binder;
        #endregion members

        /// <summary>
        /// Operation converting data from IValueProvider into the properties of passed [T] item.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="item"></param>
        /// <param name="valueProvider"></param>
        /// <param name="prefix"></param>
        /// <param name="includeProperties"></param>
        /// <param name="excludeProperties"></param>
        /// <returns></returns>
        public virtual bool Bind<T>(T item, IValueProvider valueProvider, string prefix = null, IEnumerable<string> includeProperties = null, IEnumerable<string> excludeProperties = null)
            where T : class
        {
            if (item.IsNull())
            {
                this.PublishWarning(Str.Messages.CannotBindNull1
                    , typeof(T).Name);
                return false;
            }

            var bindingContext = new ModelBindingContext
            {
                ValueProvider = valueProvider,
                ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => item, typeof(T)),
                ModelState = new ModelStateDictionary(),
                PropertyFilter = (propertyName => IsPropertyAllowed(propertyName, includeProperties, excludeProperties)),
            };

            if (prefix.IsNotEmpty())
            {
                bindingContext.ModelName = prefix;
            }

            ModelBinder.BindModel(ControllerContext, bindingContext);

            FillBindingResults<T>(bindingContext);

            return bindingContext.ModelState.IsValid;
        }

        #region protected
        protected virtual void FillBindingResults<T>(ModelBindingContext bindingContext)
        {
            if (!bindingContext.ModelState.IsValid)
            {
                foreach (var message in bindingContext.ModelState.Where(pair => pair.Value.Errors.Count > 0))
                {
                    var val = message.Value.Value.Is()
                                  ? message.Value.Value.AttemptedValue
                                  : message.Value.Errors[0].ErrorMessage.Is()
                                        ? message.Value.Errors[0].ErrorMessage
                                        : message.Value.Errors[0].Exception.Message;

                    var entity = bindingContext.Model as IPersistentObject;
                    var itemId = entity.IsNull()
                               ? "N/A"
                               : entity.IsTransient() ? "new" : entity.IDToDisplay;

                    this.PublishError(Constants.Messages.BindingError4
                        , message.Key.GetLocalized(typeof(T).Name)
                        , typeof(T).Name.GetLocalized()
                        , itemId
                        , val);
                }
            }
        }

        public static bool IsPropertyAllowed(string propertyName, IEnumerable<string> includeProperties, IEnumerable<string> excludeProperties)
        {
            var isIncluded = includeProperties.IsEmpty()
                || includeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
            var isExcluded = excludeProperties.IsNotEmpty()
                && excludeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
            return isIncluded && !isExcluded;
        }
        #endregion protected

        #region Messages
        /// <summary>
        ///     Messages collection must be provided from caller as a reference.
        ///     Contained information (if any) should be handled
        /// </summary>
        public virtual Messages Messages { get; set; }
        #endregion Messages

        #region properties
        protected readonly ControllerContext ControllerContext = new ControllerContext();
        /// <summary>
        /// The System.Web.Mvc binder.
        /// Default represenation in ProjectBase is a SmartBinder
        /// </summary>
        public virtual IModelBinder ModelBinder
        {
            protected get { return _binder ?? Binder; }
            set { _binder = value; }
        }

        protected static readonly IModelBinder Binder = new SmartBinder();
        #endregion properties
    }
}
